Generating a Resource
Editing a Material
We covered a lot in the previous chapter, but the knowledge gained isn’t super flexible because we had to cover so much. In this chapter and the next we’ll review these concepts and look at different options and how we can use them in new ways.
The first variation, covered in this chapter, is editing an existing Material instead of creating a new one.
The edit
Controller function
Let’s look at new
and edit
side by side.
def new(conn, _params) do
changeset = Trade.change_material(%Material{})
render(conn, "new.html", changeset: changeset)
end
def edit(conn, %{"id" => id}) do
material = Trade.get_material!(id)
changeset = Trade.change_material(material)
render(conn, "edit.html", material: material, changeset: changeset)
end
They’re similar in structure, although edit
is a bit more complex.
First, instead of starting with a blank Schema (%Material{}
), we’re getting a previously-created material from the database. We’re then feeding that into Trade.change_material
, so our Changeset will contain the previously assigned values.
Second, when rendering, we render the edit
template instead of the new
template.
Finally, we send the material
down alongside the changeset
.
The edit
Template
This is what gets rendered:
<h1>Edit Material</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.material_path(@conn, :update, @material)) %>
<span><%= link "Back", to: Routes.material_path(@conn, :index) %></span>
This is very similar to the new
template. The only differences are that instead of saying “Create Material” we say “Edit Material”, and the action is update
instead of create
. The update
action is the only place we use the @material
we assigned.
The form
that we render is the exact same as the one we rendered in new
. This reuse is the reason we put it in a separate template file. The only difference is that the Changeset we give it already has values for the fields, so they’ll already be filled out.
Then, when the user hits the submit button, it will go to the update
function in the MaterialController
.
The update
Controller function
Once again, we’ll compare what we did last chapter with the new function.
def create(conn, %{"material" => material_params}) do
case Trade.create_material(material_params) do
{:ok, material} ->
conn
|> put_flash(:info, "Material created successfully.")
|> redirect(to: Routes.material_path(conn, :show, material))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def update(conn, %{"id" => id, "material" => material_params}) do
material = Trade.get_material!(id)
case Trade.update_material(material, material_params) do
{:ok, material} ->
conn
|> put_flash(:info, "Material updated successfully.")
|> redirect(to: Routes.material_path(conn, :show, material))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", material: material, changeset: changeset)
end
end
As you can see, create
and update
are structured very similarly. They both destructure params, then they have a case statement where the expression is a call to the Trade
context, and then they have success and failure conditions which match based on the same tuples. The success
condition is, in fact, the almost the exact same, except the message says “updated” instead of “created”. The error
condition is similar, except it renders edit
instead of new
, and passes down a material
in addition to a changeset
.
However, despite the significant similarities, there are some important differences.
First, in update
, we’re working with a pre-existing material, so we match the "id"
parameter, then use that to get the material (Trade.get_material!(id)
), and feed that material into Trade.update_material(material, material_params)
.
The other major difference is hidden in Trade.update_material
, so let’s look at that.
def create_material(attrs \\ %{}) do
%Material{}
|> Material.changeset(attrs)
|> Repo.insert()
end
def update_material(%Material{} = material, attrs) do
material
|> Material.changeset(attrs)
|> Repo.update()
end
As you can see, we’re working with the pre-existing material, instead of a blank %Material{}
Schema. Then we pass it into the changeset with the new attributes, as before. Then, instead of inserting a new database record, we update the existing database record.
Conclusion
As promised, this was much shorter than the previous chapter. Instead of learning all new concepts, we only had to repurpose what we’d already learned.
The biggest difference is that we were working with an already-existing material, so from the start our Changeset had valid data.
Exercises
Let’s test some edge cases.
- In
update
, feedTrade.update_material
an empty%Material{}
instead of the material from the database for the first argument. What errors occur? Why? - In
update
, feedTrade.update_material
an empty%{}
instead of the material_params from the POST call for the second argument. What errors occur? Why? - In
edit
, feedTrade.change_material
an empty%Material{}
instead of the material from the database. Leave everything else the same. What errors occur? Why?
Buy the Ebook